package com.liruo.idworker.distributed.twitter;

import com.liruo.idworker.distributed.DistributedIdWorker;
import com.liruo.idworker.distributed.twitter.strategy.MachineIDStrategy;
import com.liruo.idworker.distributed.twitter.strategy.TimestampStrategy;
import com.liruo.idworker.exception.OutOfRangeException;
import com.liruo.treasureattic.common.util.BitUtils;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author:liruo
 * @Date:2023-02-06-19:01:09
 * @Desc 64bit  针对的是每一台机器
 *    41位是时间毫秒差：可以是从项目上线开始的差值，2,199,023,255,552ms--约610,839h--25,451天--848月(30天)-70年
 *    10位机器号：可以是5位数据中心，5位机器id(随意)，共1024台机器
 *    12位id位：每个机器毫秒产生的id--4096个
 *
 * 默认系统时区,可以通过TimeUtils临时修改时区
 */
@Slf4j
public class SnowflakeDistributedIdWork implements DistributedIdWorker {
    private final TimestampStrategy timestampStrategy;
    private  final MachineIDStrategy machineIDStrategy;

    public static final int MAX_MILLIS_SEQUENCE_ID =   0xfff;//4096
    private final AtomicInteger atomicInteger;

    public SnowflakeDistributedIdWork(MachineIDStrategy machineIDStrategy) {
        this(machineIDStrategy, new AtomicInteger());
    }
    public SnowflakeDistributedIdWork(MachineIDStrategy machineIDStrategy, AtomicInteger atomicInteger) {
        this(new DefaultTimestampStrategy(Instant.now().toEpochMilli()), machineIDStrategy, atomicInteger);
    }
    public SnowflakeDistributedIdWork(TimestampStrategy timestampStrategy, MachineIDStrategy machineIDStrategy, AtomicInteger atomicInteger) {
        this.timestampStrategy = timestampStrategy;
        this.machineIDStrategy = machineIDStrategy;
        this.atomicInteger = atomicInteger;
    }
    @Override
    public long getId() {
        long timestamp = this.timestampStrategy.getTimestamp();
        long machineID = this.machineIDStrategy.getMachineID();
        int incSequenceId = this.atomicInteger.getAndIncrement();

        checkSequenceRange(incSequenceId);
        return BitUtils.rmToTarget(timestamp, 62, 41)
                | BitUtils.rmToTarget(machineID, 62, 41, 10)
                | incSequenceId
                ;
    }

    private void checkSequenceRange(int incSequenceId) {
        if(incSequenceId > MAX_MILLIS_SEQUENCE_ID){
            throw new OutOfRangeException("the sequence is second or milli is use out, max=" + MAX_MILLIS_SEQUENCE_ID);
        }
    }

}
